package com.kamistoat.meimeistore.search.service.Impl;

import com.alibaba.fastjson.JSON;
import com.kamistoat.common.To.EsTo.SpuUpTo;
import com.kamistoat.meimeistore.search.config.ElasticSearchConfig;
import com.kamistoat.meimeistore.search.constant.ESConstant;
import com.kamistoat.meimeistore.search.vo.*;
import com.kamistoat.meimeistore.search.service.ListSearchService;
import com.kamistoat.meimeistore.search.vo.*;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class ListSearchServiceImpl implements ListSearchService {

    @Autowired
    RestHighLevelClient restHighLevelClient;

    /**
     * 自己编写的接口，thymeleaf专用
     * 根据检索条件查询商品
     *
     * @param searchParamVo 检索条件Vo
     * @return 商品
     */
    @Override
    public SearchResponseVo search(SearchParamVo searchParamVo) {
        // 准备检索请求
        SearchRequest searchRequestES = BuildSearchRequest(searchParamVo);
        SearchResponseVo searchResponseVo = new SearchResponseVo();
        try {
            // 执行检索请求
            SearchResponse searchResponseES =
                    restHighLevelClient.search(searchRequestES, ElasticSearchConfig.COMMON_OPTIONS);
            // 封装响应数据
            searchResponseVo = BuildSearchResponseVo(searchParamVo, searchResponseES);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return searchResponseVo;
    }


    /**
     * 自己抽取的一个方法，用于将SearchParamVo封装成ES能用的SearchRequest，在上面的方法中被调用
     *
     * @param searchParamVo 前端的搜索参数
     * @return SearchRequest
     */
    private SearchRequest BuildSearchRequest(SearchParamVo searchParamVo) {
        // 首先要创建一个检索请求库类SearchRequest
        SearchRequest searchRequest = new SearchRequest(ESConstant.PRODUCT_INDEX);  // 也可以searchRequest.indices()
        // 使用SearchSourceBuilder库类来构建检索条件
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        // 所有的根级别操作命令都可以用searchSourceBuilder.xxx来做
        // 例如现在我们有query/sort/from/size/highlight/aggs，都可以直接用searchSourceBuilder.对应操作名(传入对应构造器)来做。


        /**
         * 参照DLS命令，第一个根操作是query，下面构建这个query
         */
        // QueryBuilders是泛型类，只能构建别人不能构建自身。
        // 第一个是个Bool，构建它，无需传入参数
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();

        // 1)
        // 只有key不为空时才进行must查询
        if (!StringUtils.isEmpty(searchParamVo.getKeyword())) {
            // must查询里是一个match查询，因此构建这个match
            MatchQueryBuilder skuTitleMatch = new MatchQueryBuilder("skuTitle", searchParamVo.getKeyword());
            // 把这个match放到bool.must中
            boolQueryBuilder.must(skuTitleMatch);
        }
        // 2)
        // 只有catalogId不为空
        if (searchParamVo.getCatalog3Id() != null) {
            // 这是一个term，因此构建这个term，然后放到bool.filter中
            TermQueryBuilder catalogTerm = new TermQueryBuilder("catalogId", searchParamVo.getCatalog3Id());
            boolQueryBuilder.filter(catalogTerm);
        }
        // 3)
        // 只有brandId不为空，注意这个是列表
        if (searchParamVo.getBrandId() != null && searchParamVo.getBrandId().size() > 0) {
            // 这是一个terms，因此构建这个terms，然后放到bool.filter中
            TermsQueryBuilder brandTerms = new TermsQueryBuilder("brandId", searchParamVo.getBrandId());
            boolQueryBuilder.filter(brandTerms);
        }
        // 4)
        // 只有hasStock不为空
        if (searchParamVo.getHasStock() != null) {
            TermQueryBuilder stockTerm = new TermQueryBuilder("hasStock", searchParamVo.getHasStock() == 1 ? "true" : "false");
            boolQueryBuilder.filter(stockTerm);
        }
        // 5)
        // 只有设置了价格区间。=400_500价格区间，=_500价格500以下，=500_价格500以上。用下划线_分割
        if (!StringUtils.isEmpty(searchParamVo.getSkuPrice())) {
            // 这是一个Range，因此构建这个Range然后放到bool.filter中
            RangeQueryBuilder priceRange = new RangeQueryBuilder("price");
            // 先分割
            String[] split = searchParamVo.getSkuPrice().split("_");
            if (searchParamVo.getSkuPrice().startsWith("_")) {
                priceRange.lte(Double.parseDouble(split[1]));
            } else if (searchParamVo.getSkuPrice().endsWith("_")) {
                priceRange.gte(Double.parseDouble(split[0]));
            } else {
                priceRange.gte(Double.parseDouble(split[0]));
                priceRange.lte(Double.parseDouble(split[1]));
            }
            boolQueryBuilder.filter(priceRange);
        }
        // 6)
        // 按照属性过滤，注意属性名是在整个filter下，进行nested-query-bool查询
        // 而属性值还得再来一个filter，是在filter-nested-bool-filter-nested-bool查询
        if (searchParamVo.getAttrs() != null && searchParamVo.getAttrs().size() > 0) {
            // 是很多个must，每个must的内容是  =2_4G:5G:全网通
            // 用增强for，把每一个属性id做成term，value做成terms，放到must中，再放到bool中，再放到nested中
            for (String attr : searchParamVo.getAttrs()) {
                // 所以先构建Nested中的query-bool，最后构建Nested把query-bool放进去。
                BoolQueryBuilder attrsBool = new BoolQueryBuilder();
                // 构建term和terms，并放到must中
                String[] attrIV = attr.split("_");  // attrIV[0]:"2", attrIV[1]:"4G:5G:全网通"
                List<String> attrValues = Arrays.asList(attrIV[1].split(":"));
                TermQueryBuilder attrIDTerm = new TermQueryBuilder("attrs.attrId", attrIV[0]);
                attrsBool.must(attrIDTerm);
                // 属性值还得再来一个
                for (String attrValue : attrValues) {
                    BoolQueryBuilder attrValueBool = new BoolQueryBuilder();
                    TermQueryBuilder attrValueTerm = new TermQueryBuilder("attrs.attrValues.attrValue", attrValue);
                    attrValueBool.must(attrValueTerm);
                    NestedQueryBuilder attrValueNested = new NestedQueryBuilder("attrs.attrValues", attrValueBool, ScoreMode.None);
                    attrsBool.filter(attrValueNested);
                }
//                TermsQueryBuilder attrValueTerms = new TermsQueryBuilder("attrs.attrValue", attrValue);
//                attrsBool.must(attrValueTerms);
                // 每个must生成一个nested
                // NestedQueryBuilder传入 (path,queryBuilder,ScoreMode.评分标准)
                NestedQueryBuilder nestedQueryBuilder = new NestedQueryBuilder("attrs", attrsBool, ScoreMode.None);
                // 每个nested都要放到bool.filter下
                boolQueryBuilder.filter(nestedQueryBuilder);
            }
        }
        // 最后把整个bool放到searchSourceBuilder中
        searchSourceBuilder.query(boolQueryBuilder);



        /**
         * 参照DLS命令，第二个根操作是sort，下面构建这个sort
         */
        // 如果页面要求排序:saleCount_acs/saleCount_desc/price_acs/price_desc/hotScore_asc/hotScore_desc
        if (!StringUtils.isEmpty(searchParamVo.getSort())) {
            String[] sort = searchParamVo.getSort().split("_");
            searchSourceBuilder.sort(sort[0], sort[1].equalsIgnoreCase("acs") ? SortOrder.ASC : SortOrder.DESC);
        }

        /**
         * 参照DLS命令，第三个根操作是分页，下面构建分页
         */
        // 每次前端传递来的 PageNum 代表下一次要显示第几页。那么from就很好计算了
        searchSourceBuilder.from((searchParamVo.getPageNum() - 1) * ESConstant.PRODUCT_PAGESIZE);
        searchSourceBuilder.size(ESConstant.PRODUCT_PAGESIZE);

        /**
         * 参照DLS命令，第四个根操作是匹配关键字高亮
         */
        // 只有key不为空才高亮
        if (!StringUtils.isEmpty(searchParamVo.getKeyword())) {
            // 使用HighlightBuilder
            HighlightBuilder keyHighlight = new HighlightBuilder();
            // 剩下的内容完全就是看着DLS就能写出来
            keyHighlight.field("skuTitle");
            keyHighlight.preTags("<b style='color:red'>");
            keyHighlight.postTags("</b>");
            searchSourceBuilder.highlighter(keyHighlight);
        }

        /**
         * 参照DLS命令，最后一个根操作是aggs聚合，下面构建聚合操作
         */
        // 聚合操作同样也需要用AggregationBuilders来构建，所有的聚合Type都可以直接用AggregationBuilders.type来获得
        // 1)
        // 一级聚合brandId，以及其嵌套的brandName和brandImg。全部都是terms聚合，先把三个聚合定义出来，再嵌套
        TermsAggregationBuilder brand_agg =
                AggregationBuilders.terms("brand_agg");
        brand_agg.field("brandId").size(50);
        TermsAggregationBuilder brandName_agg =
                AggregationBuilders.terms("brandName_agg");
        brandName_agg.field("brandName").size(1);
        TermsAggregationBuilder brandImg_agg =
                AggregationBuilders.terms("brandImg_agg");
        brandImg_agg.field("brandImg").size(1);
        // 用.subAggregation(新的聚合操作)将聚合操作嵌套起来，用两次相当于这两个子聚合是平级的
        brand_agg.subAggregation(brandName_agg);
        brand_agg.subAggregation(brandImg_agg);
        // 把一级聚合放到searchSourceBuilder中
        searchSourceBuilder.aggregation(brand_agg);
        // 2)
        // 一级聚合catalogId，以及嵌套的catalogName
        TermsAggregationBuilder catalog_agg =
                AggregationBuilders.terms("catalog_agg");
        catalog_agg.field("catalogId").size(20);
        TermsAggregationBuilder catalogName_agg =
                AggregationBuilders.terms("catalogName_agg");
        catalogName_agg.field("catalogName").size(1);
        catalog_agg.subAggregation(catalogName_agg);
        // 把一级聚合放到searchSourceBuilder中
        searchSourceBuilder.aggregation(catalog_agg);
        // 3)
        // 一级聚合attr。注意这是一个nested
        // 首先得用NestedAggregationBuilder构建一个nested聚合
        NestedAggregationBuilder attr_agg = AggregationBuilders.nested("attr_agg", "attrs");
        // 再定义一个nested聚合用于聚合attrvalue
        NestedAggregationBuilder attrValues_agg = AggregationBuilders.nested("attrValues_agg", "attrs.attrValues");
        // 然后在nested聚合下再用subAggregation一直往下套，先把三个聚合定义出来
        TermsAggregationBuilder attrId_agg =
                AggregationBuilders.terms("attrId_agg");
        attrId_agg.field("attrs.attrId").size(20);
        TermsAggregationBuilder attrName_agg =
                AggregationBuilders.terms("attrName_agg");
        attrName_agg.field("attrs.attrName").size(1);
        TermsAggregationBuilder attrValue_agg =
                AggregationBuilders.terms("attrValue_agg");
        attrValue_agg.field("attrs.attrValues.attrValue").size(20);
        // value的聚合套在values上
        attrValues_agg.subAggregation(attrValue_agg);
        // valuse和name的聚合套在id聚合上
        attrId_agg.subAggregation(attrName_agg);
        attrId_agg.subAggregation(attrValues_agg);
        // id聚合再套在nested上
        attr_agg.subAggregation(attrId_agg);
        // 最后把一级聚合放到searchSourceBuilder中
        searchSourceBuilder.aggregation(attr_agg);
        // System.out.println(searchSourceBuilder.toString());  // 打印的检索条件与控制台写的完全相同
        // 把searchSourceBuilder构建好的检索条件放到检索请求类searchRequest中，然后返回
        searchRequest.source(searchSourceBuilder);
        return searchRequest;
    }

    /**
     * 自己抽取的一个方法，用于将ES的结果SearchResponse封装成前端能用的SearchResponseVo，在上面的方法中被调用
     *
     * @param searchResponse ES结果
     * @return SearchResponseVo
     */
    private SearchResponseVo BuildSearchResponseVo(SearchParamVo searchParamVo, SearchResponse searchResponse) {
        SearchResponseVo searchResponseVo = new SearchResponseVo();
        // 用getHits从ES响应中提取命中汇总Hits
        // 这个Hits是顶层Hits，可以去看URL的返回结果，下面包含了汇总信息和二级Hits
        SearchHits productHits = searchResponse.getHits();

        /**
         * 封装所有查询到的商品
         */
        List<SpuUpTo> productList = new ArrayList<>();
        // 顶级Hits中有汇总信息，可以用getTotalHits()得到，但是得到的不是Integer是Long，还得用Value提取
        if (productHits.getTotalHits().value != 0) {
            // 顶级Hits下的二级Hits才是真正的数据信息
            SearchHit[] productHitsList = productHits.getHits();
            // 用流处理把每一条hit中的原始数据制作成list
            productList = Arrays.asList(productHitsList).stream().map(hit -> {
                // 每一条命中hit的Source才是原始数据
                SpuUpTo sku = JSON.parseObject(hit.getSourceAsString(), SpuUpTo.class);
                // 设置高亮内容，直接用带高亮的标题替换原标题即可
                if (!StringUtils.isEmpty(searchParamVo.getKeyword())) {
                    sku.setSkuTitle(hit.getHighlightFields().
                            get("skuTitle").getFragments()[0].string());
                }
                return sku;
            }).collect(Collectors.toList());
        }
        searchResponseVo.setSearchProduct(productList);

        // 面包屑map，保存品牌、分类、属性id和名字的对应关系
        Map<String, String> breadAttrMap = new HashMap<>();

        /**
         * 封装当前所有商品涉及到的所有三级分类信息
         */
        // 用getAggregations().get("catalog_agg")从ES响应中提取对应名称的聚合结果
        // =左边用什么类型接收，得打断点看返回类型。没有TermAggregation类型，而是拆分了很多子类型
        // Long数据的Term聚合，用 ParsedLongTerms 类
        ParsedLongTerms catalog_agg = searchResponse.getAggregations().get("catalog_agg");
        List<CatalogResponseVo> catalogResponseVoList = new ArrayList<>();
        // 聚合的Bucket内是所有的结果，可能有很多个，要用for
        for (Terms.Bucket bucket : catalog_agg.getBuckets()) {
            CatalogResponseVo catalogResponseVo = new CatalogResponseVo();
            // 一级Term聚合的结果就是bucket的key，不过是一个String类型数据，变成Long需要进行转换
            catalogResponseVo.setCatalogId(Long.parseLong(bucket.getKeyAsString()));
            // 二级Term聚合的结果是嵌套在一级聚合的bucket中的，因此要先从一级聚合的bucket中拿到二级聚合
            // String数据的Term聚合，用 ParsedStringTerms 类
            ParsedStringTerms catalogName_agg = bucket.getAggregations().get("catalogName_agg");
            // 一个catalogId下肯定只会有一个catalogName，所以不用使用for遍历bucket了，直接用第一个就行了
            catalogResponseVo.setCatalogName(catalogName_agg.getBuckets().get(0).getKeyAsString());
            // 把分类ID和名字放到MAP中，给面包屑数据用
            breadAttrMap.put("catalog3Id" + bucket.getKeyAsString(), catalogName_agg.getBuckets().get(0).getKeyAsString());
            // 把设置完成的catalogResponseVo放到列表catalogResponseVoList中
            catalogResponseVoList.add(catalogResponseVo);
        }
        searchResponseVo.setSearchCatalog(catalogResponseVoList);

        /**
         * 封装当前所有商品涉及到的所有品牌信息
         */
        // 和上面完全一样的思路
        ParsedLongTerms brand_agg = searchResponse.getAggregations().get("brand_agg");
        List<BrandResponseVo> brandResponseVoList = new ArrayList<>();
        for (Terms.Bucket bucket : brand_agg.getBuckets()) {
            BrandResponseVo brandResponseVo = new BrandResponseVo();
            brandResponseVo.setBrandID(Long.parseLong(bucket.getKeyAsString()));
            ParsedStringTerms brandName_agg = bucket.getAggregations().get("brandName_agg");
            brandResponseVo.setBrandName(brandName_agg.getBuckets().get(0).getKeyAsString());
            // 把分类ID和名字放到MAP中，给面包屑数据用
            breadAttrMap.put("brandId" + bucket.getKeyAsString(), brandName_agg.getBuckets().get(0).getKeyAsString());
            ParsedStringTerms brandImg_agg = bucket.getAggregations().get("brandImg_agg");
            brandResponseVo.setBrandImg(brandImg_agg.getBuckets().get(0).getKeyAsString());
            brandResponseVoList.add(brandResponseVo);
        }
        searchResponseVo.setSearchBrand(brandResponseVoList);


        /**
         * 封装当前所有商品涉及到的所有属性信息
         */
        // 注意，属性聚合实际上有三层聚合，第一层是nested聚合，第二层是AttrID聚合，第三层是attrName，第四层是AttrValues聚合
        // 要先把nested聚合获取出来，这个聚合有特定的类 ParsedNested
        ParsedNested attr_agg = searchResponse.getAggregations().get("attr_agg");
        // 再从这个nested聚合中把attrId聚合获取出来，之后就是完全相同的思路
        ParsedLongTerms attrId_agg = attr_agg.getAggregations().get("attrId_agg");
        List<AttrResponseVo> attrResponseVoList = new ArrayList<>();
        for (Terms.Bucket bucket : attrId_agg.getBuckets()) {
            AttrResponseVo attrResponseVo = new AttrResponseVo();
            attrResponseVo.setAttrId(Long.parseLong(bucket.getKeyAsString()));
            ParsedStringTerms attrName_agg = bucket.getAggregations().get("attrName_agg");
            attrResponseVo.setAttrName(attrName_agg.getBuckets().get(0).getKeyAsString());
            // 把属性ID和名字放到MAP中，给面包屑数据用
            breadAttrMap.put("attrs" + bucket.getKeyAsString(), attrName_agg.getBuckets().get(0).getKeyAsString());
            // 把第四层聚合拿出来
            ParsedNested attrValues_agg = bucket.getAggregations().get("attrValues_agg");
            ParsedStringTerms attrValue_agg = attrValues_agg.getAggregations().get("attrValue_agg");
            List<String> attrValueList = new ArrayList<>();
            for (Terms.Bucket attrValue_bucket : attrValue_agg.getBuckets()) {
                attrValueList.add(attrValue_bucket.getKeyAsString());
            }
//            ParsedStringTerms attrValue_agg = bucket.getAggregations().get("attrValue_agg");
            // attrValue_agg的bucket中有多个key，是所有结果的全部属性值。注意如果这个属性本身是一个多选的属性:
            // 聚合key就会出现"4G;5G","4G;全网通","5G","4G"的情况，因为他们的String形式不同，所以ES认为他们确实是不相同的值
            // 所以要进行去重操作，利用set的无重复性来辅助执行
//            Set<String> attrValueSet = new HashSet<>();
//            for (Terms.Bucket attrValue_bucket : attrValue_agg.getBuckets()) {
//                // 无论如何都将key按照；进行划分，然后全放到set中，利用set性质进行去重
//                String[] attrValue = attrValue_bucket.getKeyAsString().split(";");
//                attrValueSet.addAll(Arrays.asList(attrValue));
//            }
//            List<String> attrValueList = new ArrayList<>(attrValueSet);
            attrResponseVo.setAttrValue(attrValueList);
            attrResponseVoList.add(attrResponseVo);
        }
        searchResponseVo.setSearchAttr(attrResponseVoList);

        /**
         * 封装面包屑导航数据，顺序是：品牌、分类、基本属性
         */
        List<BreadVo> breadVoList = new ArrayList<>();
        if (searchParamVo.getCatalog3Id() != null && searchParamVo.getCatalog3Id() > 0) {
            Long catalog3Id = searchParamVo.getCatalog3Id();
            BreadVo breadVo = new BreadVo();
            breadVo.setBreadId(catalog3Id);
            // 品牌面包屑的名字就是“品牌”，面包屑值才是品牌名
            breadVo.setBreadName("分类");
            breadVo.setBreadValue(breadAttrMap.get("catalog3Id" + catalog3Id.toString()));
            String queryString = searchParamVo.getQueryString();
            queryString = queryString.replace("&catalog3Id=" + catalog3Id.toString(), "");
            queryString = queryString.replace("catalog3Id=" + catalog3Id.toString(), "");
            breadVo.setBreadLink("http://search.meimeistore.com/list&search.html?" + queryString);
            breadVoList.add(breadVo);
        }
        if (searchParamVo.getBrandId() != null && searchParamVo.getBrandId().size() > 0) {
            for (Long brandId : searchParamVo.getBrandId()) {
                BreadVo breadVo = new BreadVo();
                breadVo.setBreadId(brandId);
                // 品牌面包屑的名字就是“品牌”，面包屑值才是品牌名
                breadVo.setBreadName("品牌");
                breadVo.setBreadValue(breadAttrMap.get("brandId" + brandId.toString()));
                String queryString = searchParamVo.getQueryString();
                queryString = queryString.replace("&brandId=" + brandId.toString(), "");
                queryString = queryString.replace("brandId=" + brandId.toString(), "");
                breadVo.setBreadLink("http://search.meimeistore.com/list&search.html?" + queryString);
                breadVoList.add(breadVo);
            }
        }
        // 对当前提交给ES的查询参数中的attr进行分析，
        // 注意，面包屑数据里存放的数据，是根据提交给ES的attr的数据，换而言之就是当前所有选中的属性条件
        if (searchParamVo.getAttrs() != null && searchParamVo.getAttrs().size() > 0) {
            for (String attr : searchParamVo.getAttrs()) {
                BreadVo breadVo = new BreadVo();
                // 查询参数中每个attr是一个String字符串，形式是 "attrId_4G:5G:全网通"
                // 先用=分割，再用_分割
                String[] breadSplit = attr.split("_");
                // 设置attrId和value
                breadVo.setBreadId(Long.parseLong(breadSplit[0]));
                breadVo.setBreadValue(breadSplit[1]);
                // 向属性-面包屑联动列表中添加属性id，用于前端联动显示
                searchResponseVo.getAttrBreadNavs().add(breadVo.getBreadId());
                // 根据attrId从上面构建的map中把属性名字查询出来
                breadVo.setBreadName(breadAttrMap.get("attrs" + breadVo.getBreadId().toString()));
                // 此时breadVo中的breadLink还空的，想要设置URL，得先从Controller层把URL拿过来。
                // Controller拿到的URL存放在searchParamVo中
                String queryString = searchParamVo.getQueryString();
                // 前端传来的HTTP请求是经过UTF-8编码的，因此这里也要先编码才能进行匹配替换
                String encode = null;
                try {
                    encode = URLEncoder.encode(attr, "UTF-8");
                    // 下面这句是处理前端空格是%20，后端空格是+的问题
                    // 并不会影响其他查询条件
                    encode = encode.replace("+", "%20");
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
                // 要替换两次，因为之前说了，拿到的URL是从?之后的内容，因此attrs有两种情况：
                // attrs=xxxx和&attrs=xxxx
                queryString = queryString.replace("&attrs=" + encode, "");  // &attrs一定要在替换attrs前面。不然会出错
                queryString = queryString.replace("attrs" + encode, "");
                breadVo.setBreadLink("http://search.meimeistore.com/list&search.html?" + queryString);  // 显然一开始就把整个URL拿来也没问题。只拿条件是为了提高泛用性
                breadVoList.add(breadVo);
            }
        }
        searchResponseVo.setBreadNavs(breadVoList);

        /**
         * 封装当前所有商品涉及到的分页信息
         */
        searchResponseVo.setPagaNum(searchParamVo.getPageNum());
        // 总记录数
        searchResponseVo.setTotalProductNum(productHits.getTotalHits().value);
        // 总页码
        searchResponseVo.setTotalPageNum(
                (int) Math.ceil(productHits.getTotalHits().value * 1.0 / ESConstant.PRODUCT_PAGESIZE));
        List<Integer> pageNavs = new ArrayList<>();
        for (int i = 1; i <= searchResponseVo.getTotalPageNum(); i++) {
            pageNavs.add(i);
        }
        searchResponseVo.setPageNavs(pageNavs);

        // 返回整个Vo类
        return searchResponseVo;
    }
}
